package cn.lingyangwl.framework.core.response;

import cn.lingyangwl.framework.core.response.format.ResponseFormat;
import cn.lingyangwl.framework.core.response.format.ResponseFormatHandler;
import cn.lingyangwl.framework.tool.core.exception.BaseError;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.*;
import lombok.experimental.Accessors;
import org.slf4j.MDC;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;

import java.io.Serializable;
import java.util.Optional;

/**
 * 响应体
 *
 * @author shenguangyang
 */
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class Resp<T> implements Serializable {
    private static final long serialVersionUID = 1L;

    public static String traceIdKey = "traceId";

    @Getter
    @Setter
    private Integer code;

    @Getter
    @Setter
    private Boolean success;

    @Getter
    @Setter
    private String msg;

    @Getter
    @Setter
    private T data;

    /**
     * 请求链路跟踪id
     */
    @Getter
    @Setter
    private String traceId;

    @JsonIgnore
    @JSONField(serialize = false)
    @Getter
    @Setter
    private HttpStatus status;

    public static <T> Resp<T> ok() {
        Integer successCodeValue = ResponseFormatHandler.getFormat().getResponseCode().getSuccess();
        return restResult(HttpStatus.OK, null, true, successCodeValue, "success");
    }

    public static <T> Resp<T> ok(T data) {
        Integer successCodeValue = ResponseFormatHandler.getFormat().getResponseCode().getSuccess();
        return restResult(HttpStatus.OK, data, true, successCodeValue, "success");
    }

    public static <T> Resp<T> ok(T data, String msg) {
        Integer successCodeValue = ResponseFormatHandler.getFormat().getResponseCode().getSuccess();
        return restResult(HttpStatus.OK, data, true, successCodeValue, msg);
    }

    public static <T> Resp<T> fail() {
        Integer defaultFailCodeValue = ResponseFormatHandler.getFormat().getResponseCode().getFail();
        return restResult(HttpStatus.OK, null, false, defaultFailCodeValue, "fail");
    }

    public static <T> Resp<T> fail(String msg) {
        Integer defaultFailCodeValue = ResponseFormatHandler.getFormat().getResponseCode().getFail();
        return restResult(HttpStatus.OK, null, false, defaultFailCodeValue, msg);
    }

    public static <T> Resp<T> fail(HttpStatus status, String msg) {
        Integer defaultFailCodeValue = ResponseFormatHandler.getFormat().getResponseCode().getFail();
        return restResult(status, null, false, defaultFailCodeValue, msg);
    }

    public static <T> Resp<T> fail(BaseError baseError) {
        return restResult(baseError.getStatus(),  null,false, baseError.getCode(), baseError.getMessage());
    }

    public static <T> Resp<T> fail(Integer code, String msg) {
        code = Optional.ofNullable(code).orElse(ResponseFormatHandler.getFormat().getResponseCode().getFail());
        return restResult(HttpStatus.OK, null, false, code, msg);
    }

    public static <T> Resp<T> fail(HttpStatus status, Integer code, String msg) {
        code = Optional.ofNullable(code).orElse(ResponseFormatHandler.getFormat().getResponseCode().getFail());
        return restResult(status, null, false, code, msg);
    }

    public static <T> Resp<T> fail(Integer code, String msg, T data) {
        code = Optional.ofNullable(code).orElse(ResponseFormatHandler.getFormat().getResponseCode().getFail());
        return restResult(null, data, false, code, msg);
    }

    public static <T> Resp<T> fail(HttpStatus status, Integer code, String msg, T data) {
        code = Optional.ofNullable(code).orElse(ResponseFormatHandler.getFormat().getResponseCode().getFail());
        return restResult(status, data, false, code, msg);
    }

    public static <T> Resp<T> fail(BaseError error, T data) {
        Integer code = Optional.ofNullable(error.getCode()).orElse(ResponseFormatHandler.getFormat().getResponseCode().getFail());
        return restResult(error.getStatus(), data, false, code, error.getMessage());
    }

    // 链式调用 ======================================================================

    public Resp<T> code(Integer code) {
        this.code = Optional.ofNullable(code)
                .orElse(ResponseFormatHandler.getFormat().getResponseCode().getFail());
        return this;
    }

    public Resp<T> data(T data) {
        this.data = data;
        return this;
    }

    public Resp<T> msg(String msg) {
        this.msg = msg;
        return this;
    }

    public Resp<T> codeAndMsg(BaseError baseError) {
        this.msg = baseError.getMessage();
        this.code = baseError.getCode();
        return this;
    }

    /**
     * 包装数据
     */
    public JSONObject wrapOfServlet() {
        ResponseFormat format = ResponseFormatHandler.getFormat();
        return wrap(format);
    }

    public JSONObject wrapReactive(HttpHeaders httpHeaders) {
        ResponseFormat format = ResponseFormatHandler.getFormat(httpHeaders);
        return wrap(format);
    }

    private JSONObject wrap(ResponseFormat format) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put(format.getMessageName(), this.msg);
        jsonObject.put(format.getCodeName(), this.code);
        jsonObject.put(format.getDataName(), this.data);
        jsonObject.put(format.getSuccessName(), this.success);
        jsonObject.put(ResponseFormat.TRACE_ID, this.traceId);
        return jsonObject;
    }

    public static <T> Resp<T> restResult(HttpStatus status, T data, boolean success, Integer code, String msg) {
        status = Optional.ofNullable(status).orElse(HttpStatus.OK);
        Resp<T> apiResult = new Resp<>();
        apiResult.setCode(code);
        apiResult.setData(data);
        apiResult.setStatus(status);
        apiResult.setMsg(msg);
        apiResult.setSuccess(success);
        apiResult.setTraceId(Optional.ofNullable(MDC.get(traceIdKey)).orElse(""));
        return apiResult;
    }

    public static void setTraceIdKey(String traceIdKey) {
        Resp.traceIdKey = traceIdKey;
    }
}
